package com.hwd.gmall.seckill.listener;

import com.alibaba.fastjson.JSONObject;
import com.hwd.gmall.common.aspect.ListenMessage;
import com.hwd.gmall.common.constant.SeckillConst;
import com.hwd.gmall.common.enums.SeckillStatusEnum;
import com.hwd.gmall.common.util.MyThreadLocal;
import com.hwd.gmall.model.activity.SeckillGoods;
import com.hwd.gmall.seckill.mapper.SeckillOrderMapper;
import com.hwd.gmall.seckill.pojo.SeckillOrder;
import com.hwd.gmall.seckill.pojo.UserRecode;
import com.hwd.gmall.seckill.util.DateUtil;
import com.rabbitmq.client.Channel;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 监听秒杀下单的消息消费者
 *
 * @author 黄伟东/Victor
 * @date 2022/5/11 20:27
 */
@Component
@Log4j2
public class SeckillOrderAddMessageListener {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private SeckillOrderMapper seckillOrderMapper;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    /**
     * 监听秒杀下单的消息
     *
     * @param message        消息体
     * @param ignoredChannel 连接
     */
    @RabbitListener(queues = SeckillConst.SECKILL_ORDER_QUEUE)
    @ListenMessage(messageDescription = "秒杀下单的排队消息")
    public void getOrderPayResult(Message message, Channel ignoredChannel) throws Exception {
        // 秒杀下单
        addSeckillOrder(new String(message.getBody()));
    }

    /**
     * 秒杀下单
     *
     * @param messageString 用户排队信息
     */
    private void addSeckillOrder(String messageString) throws Exception {
        // 获取用户名
        String username = MyThreadLocal.get();
        // 获取消息的内容，用户排队的消息
        UserRecode userRecode = JSONObject.parseObject(messageString, UserRecode.class);
        // 1. 获取时间段
        String time = userRecode.getTime();
        // 2. 获取商品id
        String goodsId = userRecode.getGoodsId();
        // 3. 获取购买数量
        Integer num = userRecode.getNum();

        // 判断商品是否在活动时间以内
        String now = DateUtil.data2str(DateUtil.getDateMenus().get(0), DateUtil.PATTERN_YYYYMMDDHH);
        if (!now.equals(time)) {
            addOrderFail("秒杀失败,商品不在活动时间以内!", userRecode, username, SeckillStatusEnum.THREE.getStatus());
            return;
        }

        // 判断商品在redis中是否存在
        SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.opsForHash().get(time, goodsId);
        if (seckillGoods == null) {
            addOrderFail("秒杀失败,商品不存在!", userRecode, username, SeckillStatusEnum.THREE.getStatus());
            return;
        }

        // 判断库存是否超出用户购买的数量
        if (num > seckillGoods.getSeckillLimit()) {
            addOrderFail("秒杀失败,超出商品的购买限制!", userRecode, username, SeckillStatusEnum.THREE.getStatus());
            return;
        }

        // 扣减库存
        if (!deductStockQueue(userRecode)) {
            addOrderFail("秒杀失败,商品库存不足!", userRecode, username, SeckillStatusEnum.THREE.getStatus());
            return;
        }

        // 包装秒杀订单对象
        SeckillOrder seckillOrder = new SeckillOrder();
        seckillOrder.setId(UUID.randomUUID().toString().replace("-", ""));
        seckillOrder.setGoodsId(goodsId);
        seckillOrder.setNum(num);
        seckillOrder.setMoney(seckillGoods.getPrice().multiply(new BigDecimal(num)));
        seckillOrder.setUserId(username);
        seckillOrder.setCreateTime(new Date());
        seckillOrder.setPayTime(new Date());
        seckillOrder.setStatus("0");

        // 将秒杀的订单对象存储异步写数据库的操作
        CompletableFuture<Boolean> toDbFuture = CompletableFuture.supplyAsync(() -> {
            int insert = seckillOrderMapper.insert(seckillOrder);
            if (insert <= 0) {
                throw new RuntimeException("订单写入数据库失败!!!");
            }
            return true;
        }, threadPoolExecutor).exceptionally(ex -> false);

        // 将秒杀的订单对象存储异步写redis的操作
        CompletableFuture<Boolean> toRedisFuture = CompletableFuture.supplyAsync(() -> {
            redisTemplate.opsForHash().put(SeckillConst.SECKILL_USER_ORDER, seckillOrder.getId(), seckillOrder);
            return true;
        }, threadPoolExecutor).exceptionally(ex -> false);

        // 只要数据库和redis任意一个写入成功，即秒杀成功
        if (!toDbFuture.get() && !toRedisFuture.get()) {
            addOrderFail("秒杀失败,系统异常,请重试!", userRecode, username, SeckillStatusEnum.THREE.getStatus());
        }

        // 扣减库存
        Long stockNum = redisTemplate.opsForHash()
                .increment(SeckillConst.SECKILL_GOODS_STOCK + time, goodsId, -num);

        seckillGoods.setStockCount(stockNum.intValue());
        redisTemplate.opsForHash().put(time, goodsId, seckillGoods);

        // 更新排队信息
        userRecode.setMoney(seckillOrder.getMoney());
        userRecode.setOrderId(seckillOrder.getId());
        addOrderFail("秒杀成功,等待支付", userRecode, username, SeckillStatusEnum.TWE.getStatus());

        // 发送延迟消息，10分钟后防止用户一直不付钱
        rabbitTemplate.convertAndSend(
                SeckillConst.SECKILL_ORDER_NORMAL_EXCHANGE,
                SeckillConst.SECKILL_ORDER_DEAD_ROUTING_KEY,
                seckillOrder.getId(),
                message -> {
                    //设置延迟消息: 10分钟后不付钱取消订单
                    message.getMessageProperties().setExpiration((10 * 60 * 1000) + "");
                    return message;
                });
    }

    /**
     * 扣减库存
     * 0--->null
     * 1--->1
     * 2---->2
     * 3---->3
     * <p>
     * n--->n
     *
     * @param userRecode 用户排队的信息
     * @return boolean
     */
    private boolean deductStockQueue(UserRecode userRecode) {
        // 获取用户买几个商品
        Integer num = userRecode.getNum();
        String goodsId = userRecode.getGoodsId();

        for (int i = 0; i < num; i++) {

            // 获取防止用户重复秒杀下单的商品库存计数器在redis中的key
            String goodsStockCountKey = SeckillConst.SECKILL_GOODS_STOCK_COUNT_QUEUE + goodsId;

            // 判断商品的库存是否足够，从商品库存队列中出列元素，不为空才可以下单
            if (redisTemplate.opsForList().rightPop(goodsStockCountKey) == null) {
                // 回滚操作
                if (i > 0) {
                    redisTemplate.opsForList().leftPushAll(goodsStockCountKey, getGoodsIds(goodsId, i));
                }
                return false;
            }
        }
        return true;
    }

    /**
     * 给指定的商品创建一个数组，长度为商品库存长度，元素全部是当前商品id
     *
     * @param goodsId    商品id
     * @param stockCount 商品库存
     * @return java.lang.String[]
     */
    private String[] getGoodsIds(String goodsId, Integer stockCount) {
        String[] ids = new String[stockCount];
        for (int i = 0; i < stockCount; i++) {
            ids[i] = goodsId;
        }
        return ids;
    }

    /**
     * 秒杀下单失败时候的处理
     *
     * @param msg        秒杀失败描述
     * @param userRecode 用户排队信息
     * @param username   用户名
     * @param status     秒杀状态
     */
    private void addOrderFail(String msg, UserRecode userRecode, String username, Integer status) {
        // 设置秒杀失败
        userRecode.setStatus(status);
        userRecode.setMsg(msg);
        // 更新redis中排队信息
        redisTemplate.opsForHash().put("User_Recode_Info", username, userRecode);
        if (status.equals(SeckillStatusEnum.THREE.getStatus())) {
            // 秒杀失败,删除用户的排队计数器
            redisTemplate.delete(SeckillConst.USER_QUEUE_COUNT + username);
        }
    }
}
